#ifndef CSVREADER_H_JM_20121130
#define CSVREADER_H_JM_20121130

// ## jmlib
#include "../NoType.h"
#include "../cast.h"
// ## c++
#include <string>
#include <fstream>
#include <vector>

namespace jmlib {

	void tokenize(std::vector<std::string>&tokens, const std::string& txt, 
		const char delimiter);
	

	template<class DataTarget, typename T1, typename T2 = NoType, 
	typename T3 = NoType, typename T4 = NoType, typename T5 = NoType, 
	typename T6 = NoType, typename T7 = NoType, typename T8 = NoType>
	class  CsvReader {
	public:

		enum SupportedType { TYPE_1, TYPE_2, TYPE_3, TYPE_4, TYPE_5, TYPE_6, 
			TYPE_7, TYPE_8 };

		void readCsv(DataTarget* table, const std::string filePath) {
			std::ifstream stream(filePath);
			if (stream.is_open()) {
				readCsv(table, stream);
			}
			else {
				throw std::invalid_argument(filePath + " is not valid csv file!");
			}
		}

		void readCsv(DataTarget* table, std::istream& stream) {
			std::string row;
			if (stream.good()) {
				getline(stream, row);				
				checkDelimiter(row);			
				detectTypes(row);
				table->addColumns(types_.size());
				addRow(table, row);
			}
			while (stream.good()) {
				getline(stream, row);
				addRow(table, row);
			}
		}

	protected:
		//typedef boost::escaped_list_separator<char> separator;
		//typedef boost::tokenizer<separator> tokenizer;

		void checkDelimiter(const std::string row) {
			size_t commas = 0;
			size_t semicolons = 0;
			for (auto it = row.begin(), end = row.end(); it != end; ++it) {
				if (*it == ',') ++commas;
				else if (*it == ';') ++semicolons;
			}
			delimiter_ = commas >= semicolons ? ',' : ';';
		} 


		void detectTypes(const std::string row) {
			/*tokenizer tokens(row, separator('\\', delimiter_, '"'));*/
			std::vector<std::string> tokens;
			tokenize(tokens, row, delimiter_);
			for (auto it = tokens.begin(), end = tokens.end(); it != end ;++it) {
				if (tryConvert<T1>(*it)) { types_.push_back(TYPE_1); }
				else if (tryConvert<T2>(*it)) { types_.push_back(TYPE_2); }
				else if (tryConvert<T3>(*it)) { types_.push_back(TYPE_3); }
				else if (tryConvert<T4>(*it)) { types_.push_back(TYPE_4); }
				else if (tryConvert<T5>(*it)) { types_.push_back(TYPE_5); }
				else if (tryConvert<T6>(*it)) { types_.push_back(TYPE_6); }
				else if (tryConvert<T7>(*it)) { types_.push_back(TYPE_7); }
				else if (tryConvert<T8>(*it)) { types_.push_back(TYPE_8); }
				else { assert(false); }
			}
		}

		void addRow(DataTarget* table, const std::string row) {
			size_t columnIndex = 0;
			//tokenizer tokens(row, separator('\\', delimiter_, '"'));
			std::vector<std::string> tokens;
			tokenize(tokens, row, delimiter_);
			for (auto it = tokens.begin(), end = tokens.end(); it != end ;++it) {

				assert(columnIndex < types_.size() 
					&& types_.size() == table->getColumnCount());


				switch (types_[columnIndex]) {
				case TYPE_1:
					table->getColumn(columnIndex)->addValueCell(convert<T1>(*it));
					break;
				case TYPE_2:
					table->getColumn(columnIndex)->addValueCell(convert<T2>(*it));
					break;
				case TYPE_3:
					table->getColumn(columnIndex)->addValueCell(convert<T3>(*it));
					break;
				case TYPE_4:
					table->getColumn(columnIndex)->addValueCell(convert<T4>(*it));
					break;
				case TYPE_5:
					table->getColumn(columnIndex)->addValueCell(convert<T5>(*it));
					break;
				case TYPE_6:
					table->getColumn(columnIndex)->addValueCell(convert<T6>(*it));
					break;
				case TYPE_7:
					table->getColumn(columnIndex)->addValueCell(convert<T7>(*it));
					break;
				case TYPE_8:
					table->getColumn(columnIndex)->addValueCell(convert<T8>(*it));
					break;

				default:
					assert(false);
				}
				++columnIndex;
			}

		}
	private:
		std::vector<SupportedType> types_;
		char delimiter_;
	};

} // namespace jmlib


#endif
